const { Error } = require('./error');
const { token, Comment, Token, String } = require('./token');
const { parse,
    Quote, Cond, Define, SetValue, Lambda, Apply, Begin } = require('./parse')
const { compileAST } = require('./compile')

var readline = require('readline');

const fs = require("fs");
let path = require("path");
const repl = require('repl');

const buildinJS = readFileSync(path.join(__dirname, "buildin.js"));
const buildinSS = readFileSync(path.join(__dirname, "buildin.ss"));

const interactive = process.argv.indexOf("-i") !== -1
const debug = process.argv.indexOf("-d") !== -1
const help = process.argv.indexOf("-h") !== -1
const preload = strOpt(process.argv, "--load")
const preloads = preload === "" ? [] : preload.split(",")
const runFile = strOpt(process.argv, "--run")
const file = strOpt(process.argv, "-f")

var codeCollect = function (line) {
    console.log(line, ";")
}
var load = function (file) {
    const str = fs.readFileSync(file).toString();
    var ast = parse(token(str))
    if (ast instanceof Error) {
        // todo 报错打印对应的行
        let n = 0
        let line = 1
        for (; ;) {
            n += lines[line].length + 1
            if (n > ast.pos) {
                console.log(line, lines[line].length - (n - code.pos))
                break
            }
            line++
        }
        return false
    }
    var code = compileAST(ast)
    codeCollect(code)
    // codeLines = code.split(/\r|\n|\r\n/).filter(l => l !== "")
    // for (let line of codeLines) {
    //     if (line.indexOf("(load)") === 0) {
    //         if (!evalJS(line)) {
    //             return false
    //         }
    //     } else {
    //         codeCollect(line)
    //     }
    // }
    return true
}
var runtimeLoad = load;

if (help) {
    console.log(process.argv[1], "Usage:")
    console.log("\tcat src.ss | node main.js > dist.js")
    console.log("\t-f", "input file")
    console.log("\t-i", "interactive repl")
    console.log("\t-d", "when in repl, print compiled js")
    console.log("\t-h", "show this help")
    console.log("\t--load", "load files, separated by ','")
    console.log("\t--run", "run file")
} else if (runFile !== "") {
    if (debug) {
        console.log(buildinJS)
    }
    eval(buildinJS)
    const buildinSSJS = compileAST(parse(token(buildinSS)))
    if (debug) {
        console.log(buildinSSJS)
    }
    eval(buildinSSJS)
    let codes = []
    codeCollect = function (line) {
        codes.push(line)
    }
    if (load(runFile)) {
        if (debug) {
            console.debug(codes.join(";"))
        }
        console.log(eval(codes.join(";")))
    }
} else if (interactive) {
    eval(buildinJS)
    // let context = {}
    const buildinSSJS = compileAST(parse(token(buildinSS)))
    if (debug) {
        console.log(buildinSSJS)
    }
    eval(buildinSSJS)
    // we support eval in interactive
    // only support direct eval
    const codes = loadAndEvalScriptFile(preloads)
    for (const code of codes) {
        eval(code)
    }
    repl.start({ prompt: '> ', useGlobal: true, eval: myEval })
} else { // compile
    // do not support eval in compile mode
    console.log(buildinJS, ";");
    // console.log("var context = {};")
    const buildinSSJS = compileAST(parse(token(buildinSS)))
    console.log(buildinSSJS, ";");
    // eval(buildinSSJS)
    // for (let key in context) {
    //     eval(`var ${key}=context.${key}`)
    // }
    load(file)
}

function strOpt(argv, key) {
    const i = argv.indexOf(key)
    if (i === -1) {
        return ""
    }
    if (!(i + 1 < argv.length)) {
        return ""
    }
    return argv[i + 1]
}
function loadAndEvalScriptFile(files) {
    const r = []
    for (const file of files) {
        const content = fs.readFileSync(file);
        var code = compileAST(parse(token(content.toString())))
        console.log("load", file)
        r.push(code)
    }
    return r
}
function myEval(cmd, context, filename, callback) {
    if (cmd.trim() === "") {
        callback(null, null);
        return
    }
    runtimeLoad = function (file) {
        const contentSS = readFileSync(file);
        const ast = parse(token(contentSS))
        let code
        if (context) {
            code = compileAST(ast, context)
        } else {
            code = compileAST(ast)
        }
        if (typeof code === "string") {
            return evalJS(code)
        } else {
            console.error(code)
        }
    }
    var ast = parse(token(cmd))
    if (ast instanceof Error) {
        if (ast.name === "need )") {
            return callback(new repl.Recoverable(ast));
        }
        callback(null, ast);
        // console.error(error)
        return
    }
    var code = compileAST(toEval(ast))
    if (debug) {
        console.log(code)
    }
    const r = (eval)(code)
    for (let exp of ast.exprs) {
        if (exp instanceof Define || exp instanceof SetValue) {
            context[exp.name] = eval(exp.name)
        }
    }
    callback(null, r);
}
function readFileSync(file) {
    const content = fs.readFileSync(file);
    return content.toString();
}
function toEval(ast) {
    if (ast instanceof String) {
        return ast
    } else if (ast instanceof Token || ast instanceof SetValue) {
        return ast
    } else if (ast instanceof Quote) {
        return ast
    } else if (ast instanceof Begin) {
        ast.exprs = ast.exprs.map(toEval)
        return ast
    } else if (ast instanceof Cond) {
        ast.condValues = ast.condValues.map(function (exprs) {
            return exprs.map(toEval)
        })
        return ast
    } else if (ast instanceof Define) {
        ast.value = toEval(ast.value)
        return ast
    } else if (ast instanceof Lambda) {
        ast.exprs = ast.exprs.map(toEval)
        return ast
    } else if (ast instanceof Apply) {
        let func
        if (ast.func instanceof Token) {
            func = ast.func.value
            if (func === "eval") {
                // eval(compile(x))
                return new Apply(ast.pos, ast.func, [
                    new Apply(ast.pos, new Token("compile", ast.func.pos), ast.args)
                ])
            }
        }
        return ast
    }
}
function repr(tklst) {
    if (typeof tklst === "number") {
        return tklst.toString()
    } else if (typeof tklst === "string") {
        return `"${tklst}"` // todo escape
    } else if (Array.isArray(tklst)) {
        return "(" + tklst.map(repr).join(" ") + ")"
    } else {
        // symbol
        return tklst.value
    }
}
function compile(tklst) {
    if (Array.isArray(tklst)) {
        return compileAST(parse(token(repr(tklst))))
    }
    return tklst
}